/**
 * Copyright 2010 The Regents of the University of California
 *
 *  All Rights Reserved
 */
#include <inttypes.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <stdio.h>

#include "new-switch-handler.h"
#include "dcswitch.h"

#include "openflow/openflow.h"
#include "flow.h"
#include "ofpbuf.h"
#include "learning-switch.h"
#include "vconn.h"
#include "xtoxll.h"
#include "fault-manager.h"
#include "packets.h"
#include "arp-handling.h"
#include "build-fattree.h"



#define THIS_MODULE VLM_new_switch_handler
#include "vlog.h"


struct ping_pkt* create_ping_packet(uint8_t* eth_src, uint8_t* eth_dst, uint32_t ip_src, uint32_t ip_dst);
void send_find_level_probe(struct lswitch *sw, struct rconn *rconn, struct ofp_switch_features *osf);
void registerSwitch(struct lswitch *sw, struct rconn *rconn, struct ofp_switch_features *osf);
void send_probes(int currentIndex);
bool isDownwordFacing(int switchIndex, int port);
void catchSwitchDiscoveryTimeout(int);
void catchAlarm(int);


int n_probe_sent=0;
int n_probe_recd=0;

bool probesSent = false;

static int switch_index;
int last_edge[K];

struct dcSwitch;

struct ping_pkt
{
	struct eth_header eth;
	struct ip_header iph;
	struct icmp_header icmph;
};

void send_probe_packet_out(struct lswitch *sw, struct rconn *rconn, uint16_t in_port, int out_ports, DCSwitch_node* node);

void add_new_switch(struct lswitch *sw, struct rconn *rconn , struct ofp_switch_features *osf)
{
    int sw_index;

    assert(sw != NULL);
    assert(rconn != NULL);

    sw_index = new_lookup(sw->datapath_id);

    if (sw_index == -1) {
        VLOG_WARN("NEW SWITCH TRYING TO CONNECT ! Switch ID = %llx", sw->datapath_id);
	}
	//else 

//    if (switchMap_isConnected(sw_index)) {
//        VLOG_WARN("Switch with index %d is already connected", sw_index);
//        return;
//    }

    //setSwitchEntry(sw_index, sw, rconn, true);
	registerSwitch(sw, rconn, osf);
	switch_index++;

    VLOG_DBG("Switch with index %d connected", sw_index);

	if(probesSent == true)
	{
		VLOG_WARN("TIMER REINITIATED!");
		signal(SIGALRM, catchSwitchDiscoveryTimeout);
		alarm(getProbeTimeout());
		probesSent = false;
	}

}

void registerSwitch(struct lswitch *sw, struct rconn *rconn, struct ofp_switch_features *osf)
{
    size_t n_ports = ((ntohs(osf->header.length)
                       - offsetof(struct ofp_switch_features, ports))
                      / sizeof *osf->ports);
	int i, port_no;
	int sw_index = new_lookup(sw->datapath_id);

	struct ofp_phy_port *opp = NULL;
	DCSwitch_node* new_switch = (DCSwitch_node*)malloc(sizeof(DCSwitch_node));
			
	new_switch->index = switch_index; 
	new_switch->sw_indexTmp = sw_index;
	new_switch->fattree_index = -1;
	new_switch->pod = -1;
	new_switch->rconn = rconn;
	new_switch->sw = sw;
	new_switch->n_ports = 0;
	
	for(i=0;i<K; i++)
	{	
		new_switch->ports[i][0] = -1;
		new_switch->ports[i][1] = 0; //we begin with the assumption that all ports are downwards			
	}
	
	for(i=0; i<n_ports; i++)
	{
		opp = (struct ofp_phy_port*)&osf->ports[i];
		port_no = ntohs(opp->port_no);
		
		if(port_no > 65000) {continue;} //Hack for netfpga switches
		else
		{
			new_switch->ports[new_switch->n_ports][0] = port_no;
			new_switch->ports[new_switch->n_ports][1] = 0;
			VLOG_DBG("Switch %d port %d face %d index %d", switch_index, new_switch->ports[new_switch->n_ports][0], new_switch->ports[new_switch->n_ports][1], new_switch->n_ports);
			new_switch->n_ports++;
		}
		//we begin with the assumption that all ports are facing down
	//	VLOG_WARN("Sending probe packets from switch: %d(%d) on port %d",switch_index, sw_index, port_no);
	//	send_probe_packet_out(sw, rconn, OFPP_NONE, port_no, new_switch);
	//	n_probe_sent++;
	}


	new_switch->level = -1;  

	new_switch->datapath_id = sw->datapath_id;
	memcpy(new_switch->hw_addr, opp->hw_addr, ETH_ADDR_LEN);

	for(i=0; i<K; i++)
		new_switch->neighbours[i] = NULL;

	DCSwitches[new_switch->index] = new_switch; //have another array for quick lookup of switch_indices
	VLOG_WARN("Registered switch Index: %d\n", new_lookup(sw->datapath_id));
}


void send_probes(int currentIndex)
{
	int i,j;
	DCSwitch_node* node;
	int port_no;

	for(i=0; i< currentIndex; i++)
	{
		node = DCSwitches[i];
		if (node == NULL)
			continue;

		for(j=0; j < node->n_ports; j++)
		{		
			{
				port_no = node->ports[j][0];
			
				VLOG_DBG("Sending probe packets from switch: %d(%d) on port %d",node->index, node->sw_indexTmp, port_no);
				send_probe_packet_out(node->sw, node->rconn, OFPP_NONE, port_no, node);
				n_probe_sent++;
			}
		}
	}
	VLOG_WARN("Total probes sent: %d", n_probe_sent); 
}

	
struct ping_pkt* create_ping_packet(uint8_t* eth_src, uint8_t* eth_dst, uint32_t ip_src, uint32_t ip_dst)
{
	struct ping_pkt* pingPacket = (struct ping_pkt*)malloc(sizeof(struct ping_pkt));
	pingPacket->eth.eth_type = htons(ETH_IP_TYPE);
	memcpy(pingPacket->eth.eth_src, eth_src, ETH_ADDR_LEN);
	memcpy(pingPacket->eth.eth_dst, eth_dst, ETH_ADDR_LEN);

	pingPacket->iph.ip_ihl_ver = htons(4);
	pingPacket->iph.ip_tos = htons(0);
	pingPacket->iph.ip_tot_len = htons(0);
	pingPacket->iph.ip_id = htons(0);
	pingPacket->iph.ip_frag_off = htons(0);
	pingPacket->iph.ip_ttl = htons(33);
	pingPacket->iph.ip_proto = htons(IP_TYPE_ICMP);
	pingPacket->iph.ip_csum = 0;
	pingPacket->iph.ip_src = htonl(ip_src);
	pingPacket->iph.ip_dst = htonl(ip_dst);


	pingPacket->icmph.icmp_type = htons(ICMP_ECHO_REQ);
	pingPacket->icmph.icmp_code = 0;
	pingPacket->icmph.icmp_csum = 0;

	return ((struct ping_pkt*)pingPacket);
}

void handle_probe_packet(struct lswitch* sw, struct ofp_packet_in* _opi)
{
	int i, index;
	int in_port = ntohs(_opi->in_port);
	DCSwitch_node *node, *peer; 
	probe_pkt *receivedProbe = (probe_pkt*)_opi->data;
			
	index = new_lookup(sw->datapath_id);
	node = DCSwitches[index];
	peer = DCSwitches[(*receivedProbe).sw_index];
	VLOG_DBG("Received probe packet back %d", n_probe_recd);
	

	for(i=0; i<K ; i++)
	{
		if(node->ports[i][0] ==	in_port)
		{	
			node->neighbours[i] = peer; 
		}
	}
		 	
	n_probe_recd++;
}


void handle_level_response(struct lswitch *sw)
{
}


void send_probe_packet_out(struct lswitch *sw, struct rconn *rconn, uint16_t in_port, int out_ports, DCSwitch_node* node)
{
    struct ofpbuf *ethernetBuffer = NULL;
    struct ofpbuf *out = NULL;
    const struct ofpbuf *packet;
    uint16_t in_port_send = OFPP_NONE;
    uint16_t out_port =  out_ports;
    struct ofp_packet_out *opo;
    struct ofp_action_output *oao;
    size_t size1 = sizeof *opo + sizeof *oao;
    
    probe_pkt probe;
    size_t size = sizeof(probe_pkt); 

    uint8_t tmp[6];
	tmp[0] = 0x11;
	tmp[1] = 0x11;
	tmp[2] = 0x11;
	tmp[3] = 0x11;
	tmp[4] = 0x11;
	tmp[5] = 0x11;
	VLOG_DBG("Broadcast address: %x:%x:%x:%x:%x:%x", tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5]);
    
    memset(&probe, 0, size);
    
   
    probe.header.eth_type = htons(0x8582);
    memcpy(probe.header.eth_dst, &tmp, 6);

    probe.sw_index = node->index;
    probe.sw_indexTmp = node->sw_indexTmp;
    probe.port = out_ports;

    ethernetBuffer = ofpbuf_new(size);
    
    // create the ofpbuf envelope for the probe ethernet
    // packet
    ofpbuf_put(ethernetBuffer,
                &probe,
                size);

    packet = ethernetBuffer;
    out = ofpbuf_new(size + packet->size);

    opo = ofpbuf_put_zeros(out, size1);
    opo->header.version = OFP_VERSION;
    opo->header.type = OFPT_PACKET_OUT;
    opo->buffer_id =  -1;
    opo->in_port = htons(in_port_send);

    oao = (struct ofp_action_output *)&opo->actions[0];
    oao->type = htons(OFPAT_OUTPUT);
    oao->len = htons(sizeof *oao);
    oao->port = htons(out_port);

    opo->actions_len = htons(sizeof *oao);

    ofpbuf_put(out, packet->data, packet->size);
    update_openflow_length(out);

    ofpbuf_uninit(ethernetBuffer);
	queue_tx(sw, rconn, out);
}


bool isDownwordFacing(int switchIndex, int port)
{
	int i;		
	DCSwitch_node *node;
	node = DCSwitches[switchIndex];

	for(i=0; i<K; i++)
	{
		if(node->ports[i][0] == port)
		{
			if(node->ports[i][1] == 0)
			{
				VLOG_DBG("%d of switch %d is downword FACING",port, switchIndex) ;
				return true;
			}
			else
			{
				VLOG_DBG("%d of switch %d is upward FACING",port, switchIndex) ;
				return false;
			}
		}
	}
	return true;
}

void catchAlarm(int sig)
{
	int i,j;
	int edge_count =0, aggr_count = 0, core_count=0;
	DCSwitch_node *node;
	DCSwitch_node *peer;
	VLOG_WARN("The K value %d", K);
	for(i=0; i<MAX_SWITCHES; i++)
	{
	     node = DCSwitches[i];
	     if(node == NULL)
		 {
			VLOG_WARN("Full topology yet to be discovered!");
			continue;
		 } 
	     node->internal_ports = 0;
	
	     for(j=0; j<K ; j++)
	     {
		     //DCSwitch_node* peer = node->neighbours[j];
			if( node->neighbours[j] != NULL)
				(node->internal_ports)++;
	     }
		VLOG_DBG("Switch %d with dpid %llx has %d internal ports", node->index, node->datapath_id, node->internal_ports);
	}
	
	for(i=0; i<MAX_SWITCHES; i++)
	{
	     DCSwitch_node* node = DCSwitches[i];
		VLOG_DBG("internal ports = %d, Port count = %d", node->internal_ports, node->n_ports);
		 if(node == NULL)
			continue;
	     if (node->internal_ports <= K/2)                  // TO DO: To be replaced later with broad cast ping
	     {
		    node->level = EDGE;
		    VLOG_DBG("Switch %d with dpid %llx has %d internal ports marked as edge switch level: %d", node->index, node->datapath_id, node->internal_ports, node->level);
			edge_count++;
	     }
	
	}
	VLOG_WARN("EDGE COUNT = %d " , edge_count);
	
	for(i=0; i<MAX_SWITCHES; i++)
	{
        	node = DCSwitches[i];
		if(node == NULL) continue;
        	else if(node->level == -1)
        	{
        	    for(j=0; j < K; j++)
        	    {
        	        if( node->neighbours[j] != NULL)
					{	peer = node->neighbours[j];
						if((node->neighbours[j])->level == 0)
						{
							node->level = 1;
							aggr_count++;
			                break;
						}
					}
        	   }
       		}
	}
    VLOG_WARN("AGGR COUNT = %d " , aggr_count);

     for(i=0; i<MAX_SWITCHES; i++)
     {
        DCSwitch_node* node = DCSwitches[i];
		if(node == NULL) continue;

        if(node->level != EDGE && node->level != AGGR)
        {
			DCSwitch_node* node = DCSwitches[i];
			for(j=0; j< K; j++)
			{
				if( node->neighbours[j] != NULL)
				{
					peer = node->neighbours[j];
					if(peer->level == AGGR)
					{
						node->level = CORE;
						core_count++;
						break;
					}
				}
			}
		}
	}

	VLOG_WARN("CORE COUNT = %d " , core_count);

//Assigning port facing
	for(i=0; i<MAX_SWITCHES; i++)
	{
		node = DCSwitches[i];
		if(node	== NULL) continue;
		for(j=0; j<K; j++)
		{
			peer = node->neighbours[j];
			if(peer == NULL) 
			{
				node->ports[j][1] = 0;
				continue;
			}
			else if( node->level < peer->level)
				node->ports[j][1] = 1;
			else if( node->level > peer->level)
				node->ports[j][1] = 0;
			else
			{
				VLOG_WARN("Some bug - two switches with same level cannot be neighbours");
				node->ports[j][1] = -1;
			}
		}
	}

	for(i=0; i<MAX_SWITCHES; i++)
	{
		node = DCSwitches[i];
		if(node	== NULL) continue;
		else assert(node->level !=-1);
	}

	VLOG_DBG("Received all probes");	
	init_fattree_indices();			
}

void catchSwitchDiscoveryTimeout(int sig)
{
	VLOG_WARN("switches have come up. Now sending probes");
	n_probe_recd = 0;
	n_probe_sent = 0;
	VLOG_DBG("Before sending probes. No of switches %d", switch_index);
	send_probes(switch_index);	
	VLOG_WARN(" PROBE TIMER INITIATED!");
	signal(SIGALRM, catchAlarm);
	alarm(getProbeTimeout());
	probesSent = true;
	return;
}
